import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;

/**
 *
 * @author  Gerrit van Roekel, Peter de Bruine, Klaas Dons
 * @version 1.0
 */
public class marioCanvas extends GameCanvas implements Runnable {
    //Var gebruikt voor de muziek op de achtergrond.
    Player midiPlayer = null;
    
    //Var gebruikt voor het maken van de sprites en layers.
    private LayerManager lm;
    private Image sprite_mario;
    private Sprite mario;
    private Image sprite_backGroundLayer;
    private TiledLayer backGroundLayer;

    //Var voor het instellen van de sprite zoals lopen, snelheid e.d.
    public int marioseqDelay = 200;
    private int[] marioSeqWalk = {0,1,2};
    private int[] marioSeqJump = {5};
    private int[] marioSeqFall = {6};
    private int maxJump = 8;  // Jump speed = 3 * 1.04 net zolang totdat deze bij 4 is. Dus na 8 keer is ie bij 4, globaal dan zo'n 26 pixels gesprongen.
    private int walkSpeed = 3;
    private int jumpWalkSpeed = 2;
    
    //Var voor het updaten van de sprites. Volgende frame e.d.
    private SpriteAnimationTask spriteMarioAnimator;
    private Timer timer;
    
    //Var gebruikt tijdens het spel om bijvoorbeeld aante geven welke richting mario loopt.
    private byte lastDirection = DOWN;
    private byte lastWalkDirection = RIGHT;
    private float jumpSpeed = 0;
    private float fallingSpeed = 0;
    private boolean jumpedToMax = false;
    
   //Bij het uitvoeren van deze classe, het display fullscreen zetten, en init aanroepen, om het 
   // spel voor te bereiden.
   public marioCanvas() {
        super(true);
        try {
            this.setFullScreenMode(true);
            this.init();
        } catch (Exception ex) {
        }
    }
    
   /**
    * Bereid alles voor, voor het starten van het spel.
    */
    public void init() {
      //Maak de spirtes aan, zodat deze op het beeldscherm getoond kunnen worden.
      getSprite_mario();
      getTitleBackground();
      
      //Start het achtergrond muziekje
      playMidiBackGround();
      startMusic();
      
      
      //Maak de layer aan, en zet hierin de verschillende sprites. Dit is onze scene.
      lm = new LayerManager();
      initLayerManager(lm);
      this.lm.setViewWindow(0,0, this.getWidth(), this.getHeight());
      
      //Maak een timer aan, en ken hieraan de verschillende sprites die moeten worden geupade.
      this.timer = new Timer();
      this.spriteMarioAnimator = new SpriteAnimationTask(this.mario, true);
      this.timer.scheduleAtFixedRate(this.spriteMarioAnimator, 0, marioseqDelay);

   }

    /**
     * Ken de sprites toe aan de layer die er voor zorgt dat de sprite op het beeldscherm komt
     * @param lm, om welke layer manager gaat het.
     */
    public void initLayerManager(LayerManager lm) {
        this.mario.setPosition(55, 43);
        this.mario.setVisible(true);
        lm.append(this.mario);
        byte temp = RIGHT;
        setSpriteMarioWalk(temp);
        this.backGroundLayer.setPosition(0, 0);
        this.backGroundLayer.setVisible(true);
        lm.append(this.backGroundLayer);
    }
    
    /**
     * Run, hierin begint het spel met lopen, en alles af te handelen. 
     * ook de gamelus is hierin te vinden.
     */
    public void run() {
        Graphics g = getGraphics();
        //Game Loop
        while(true) {
          g.fillRect(0, 0, this.getWidth(), this.getHeight());
          int keyState = getKeyStates();
          if ((keyState & LEFT_PRESSED) != 0) {
                if (lastWalkDirection != LEFT) {
                  //this.lastDirection = LEFT;
                  this.lastWalkDirection = LEFT;
                  setSpriteMarioWalk(this.lastWalkDirection);
                  this.mario.move(-9, 0);
                }
                this.spriteMarioAnimator.forward();
                if (this.jumpSpeed == 0 && this.fallingSpeed == 0) {
                  this.mario.move(walkSpeed * -1, 0);  
                } else {
                  this.mario.move(jumpWalkSpeed * -1, 0);
                }
                if ((keyState & UP_PRESSED) != 0) { 
                  setToJump();
                } else {
                    this.lastDirection = DOWN;
                }
          } 
          else if ((keyState & RIGHT_PRESSED) != 0) {
              if (lastWalkDirection != RIGHT) {
                  //this.lastDirection = RIGHT;
                  this.lastWalkDirection = RIGHT;
                  setSpriteMarioWalk(this.lastWalkDirection);
                  this.mario.move(9, 0);
                }
              this.spriteMarioAnimator.forward();
              if (this.jumpSpeed == 0 && this.fallingSpeed == 0) {
                  this.mario.move(walkSpeed , 0);  
                } else {
                  this.mario.move(jumpWalkSpeed, 0);
                }
              if ((keyState & UP_PRESSED) != 0) { 
                setToJump();
              } else {
                  this.lastDirection = DOWN;
              }
          }
          else if ((keyState & UP_PRESSED) != 0) {
              setToJump();
          } else {
              this.spriteMarioAnimator.setMoving(false);
              this.lastDirection = DOWN;
          }
          
          spriteGrafity();
          updateView();
          this.lm.paint(g, 0, 0);
          flushGraphics(0, 0, this.getWidth(), this.getHeight());

          try {
            Thread.sleep(20);
          } catch (InterruptedException ex) {
          }
  
        }
    }
    public void setToJump() {
        this.mario.move(0, 1);
        if (lastDirection != UP && this.jumpedToMax == false && this.mario.collidesWith(this.backGroundLayer, true)) {
          this.lastDirection = UP;
          this.jumpSpeed = this.maxJump;
          this.fallingSpeed = 0;
          setSpriteMarioJump();
        }
        this.mario.move(0, -1);
        this.spriteMarioAnimator.forward();
    }
   
    /**
     * Controleer waar Mario zich bevind, en stel hiervoor een zwaarte kracht in,
     * zodat Mario weer naar beneden valt.
     */
    public void spriteGrafity() {
        if (this.lastWalkDirection == LEFT) {
            this.mario.move(2, 0);
        } else {
            this.mario.move(-2, 0);
        }
        if (this.mario.collidesWith(this.backGroundLayer, true) == false && (this.lastDirection != UP || this.jumpedToMax)) {
            this.fallingSpeed *= 1.04;
            this.lastDirection = DOWN;
            if (this.fallingSpeed == 0) {
                this.fallingSpeed = 3;
                this.mario.setFrameSequence(marioSeqFall);
            }
            this.mario.move(0, (int) this.fallingSpeed);
            // Nog niet zeker of dit de goede oplossing is.
            //Zorg ervoor, dat als deze toch halverwegen een blok valt, er net zolang
            //eentje omhoog wordt gegaan, totdat Mario goed staat.
            if (this.mario.collidesWith(this.backGroundLayer, true)) {
                while(this.mario.collidesWith(this.backGroundLayer, true)) {
                    this.mario.move(0, -1);
                }
                //En vervoglens, eentje omhoog, zodat de 'botsing' wordt opgelost
                this.mario.move(0, 1);
                setSpriteMarioWalk(this.lastWalkDirection);
            }
        }
        else if (this.lastDirection == UP && this.jumpedToMax == false) {
            if (this.jumpSpeed > 1) {
                this.jumpSpeed /= 1.08;
                if (this.jumpSpeed < 1) { 
                    this.jumpSpeed = 0;
                    this.jumpedToMax = true;
                }
                this.mario.move(0, (int) (this.jumpSpeed * -1));
            }
        } else {
            this.jumpSpeed = 0;
            this.fallingSpeed = 0;
            this.lastDirection = DOWN;
            this.jumpedToMax = false;
        }
        
        if (this.lastWalkDirection == LEFT) {
            this.mario.move(-2, 0);
        } else {
            this.mario.move(2, 0);
        }
        
        //Zet Mario zo, dat deze geen botsing maakt met de omgeving sprites.
        //Botsing voor LEFT
        this.mario.move(0, -1);
        while((this.mario.collidesWith(this.backGroundLayer, true) || this.mario.getX() < 0) && this.lastWalkDirection == LEFT) {
                this.mario.move(1, 0);
        }
        this.mario.move(1, 0);
        
        //Botsing voor Rechts
        while((this.mario.collidesWith(this.backGroundLayer, true) || (this.mario.getX() + this.mario.getWidth()) > this.backGroundLayer.getWidth()) && this.lastWalkDirection == RIGHT) {
                this.mario.move(-1, 0);
        }
        this.mario.move(-1, 0);
        
        //Botsing voor UP, dan maxJump aan zetten, eerst moet mario weer vallen, dan mag ie weer springen.
        while(this.mario.collidesWith(this.backGroundLayer, true) && this.lastDirection == UP) {
                this.jumpedToMax = true;
                this.mario.move(0, 1);
        }
        this.mario.move(0, 1);
       
        //GAME OVER, Mario loopt uit het scherm
        if (this.mario.getY() > this.backGroundLayer.getHeight()) {
            this.mario.setPosition(30, 30);
            this.fallingSpeed = 0;
            byte temp = RIGHT;
            setSpriteMarioWalk(temp);
        }
    }
    
    /**
     * De camera loopt op mario, dus bij alles Mario volgen met wat deze doet.
     * Enkel de X hoeft aangepast te worden.
     */
    public void updateView() {
        int x = 0;
        int y = 0;
        
        x = this.mario.getX() - (this.getWidth() / 4);
        if (x < 0) {
            x = 0;
        }
        if ((x + this.getWidth()) > this.backGroundLayer.getWidth()) {
            x =  this.backGroundLayer.getWidth() - this.getWidth();
        }
        
        this.lm.setViewWindow(x, y, this.getWidth(), this.getHeight());
    }
    
   /**
     * Stop of start met het spelen van het muziekje.
     */
   public void stopMusic() {
        try {
            midiPlayer.stop();
        } catch (MediaException ex) {
        }
   }
   public void startMusic() {
        try {
            midiPlayer.start();
        } catch (MediaException ex) {
        }
   }
   public void closeMusic() {
       midiPlayer.close();
   }

   /**
    * Zorg ervoor dat Mario eruit ziet alsof hij aan het lopen is.
    * @param Direction geef aan welke kant Mario aan het oplopen is.
    */
  public void setSpriteMarioWalk(byte Direction) {
    if (Direction == RIGHT) {
        this.mario.setTransform(Sprite.TRANS_MIRROR);
    } else {
        this.mario.setTransform(Sprite.TRANS_NONE);
    }
    this.mario.setFrameSequence(marioSeqWalk);
  }

  
  /** 
   * Zorg ervoor dat mario eruit ziet alsof hij springt.
   */
  public void setSpriteMarioJump() {
    this.mario.setFrameSequence(marioSeqJump);
  }
  
  
  /**
   * Zorg ervoor dat op de achtergrond een muziek je kan worden afgespeeld. Roep hiervoor de 
   * muziek manager aan, en maak hier op een Player, die vervolgens de streams afspeelt.
   */
    public void playMidiBackGround() {
      try {
          midiPlayer = Manager.createPlayer(getClass().getResourceAsStream("/marioTheme.mid"), "audio/midi");
          if (midiPlayer != null) {
              midiPlayer.setLoopCount(-1);
              midiPlayer.start();
          }
        } catch (Exception ex) {
        }
    }
        
    /**
     * Maak de mario sprite aan. Van het plaatje 16 breedt, bij 27 hoog.
     * Zet de eerste mario sequence als een mario die aan het lopen is.
     */
    public void getSprite_mario()  {
      try {
        if (this.sprite_mario == null) {
            this.sprite_mario = Image.createImage("/sprite_mario.png");
        }
        this.mario = new Sprite(this.sprite_mario, 16, 27);
        this.mario.setFrameSequence(marioSeqWalk);
      } catch (Exception ex) {
          ex.printStackTrace();
      }
  }
    /**
     * Maak het spel level aan.
     */    
    public void getTitleBackground()  {
        if (this.sprite_backGroundLayer == null) {
            try {
                this.sprite_backGroundLayer = Image.createImage("/sprite_bg.png");
            } catch (IOException ex) {
            }
        }
        if (this.backGroundLayer == null) {
            this.backGroundLayer = new TiledLayer(52, 20, this.sprite_backGroundLayer, 16, 16);
            int[][] tiles = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 6, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 6, 0, 0, 1, 6, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 5, 5, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 8, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 1, 2, 2, 2, 2, 3, 3, 3, 5, 5, 5, 5, 6, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 6, 0, 0, 1, 3, 6, 0, 7, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 },
{ 7, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 12, 0, 0, 0, 0, 7, 10, 10, 10, 11, 11, 12, 0, 0, 7, 11, 12, 0, 7, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 0, 0, 0 }
};
 // write mid-init user code here
for (int row = 0; row < 20; row++) {
for (int col = 0; col < 52; col++) {
                    this.backGroundLayer.setCell(col, row, tiles[row][col]);
                }
            }
        }
    }
    
    /**
     * Zorg voor de animatie van de verschillende Sprites. De class maakt gebuikt ban de TimerTask, die 
     * ervoor zorgt dat deze continue als de tijd verstreken is, zijn update uitvoert.
     * @param Sprite, om welke sprite gaat het
     * @param Boolean, moet de sprite continu worden geupdate?
     */
    private class SpriteAnimationTask extends TimerTask {

        private boolean moving = false;
        private boolean forward = true;
        private Sprite sprite;

        public SpriteAnimationTask(Sprite sprite, boolean forward) {
            this.sprite = sprite;
            this.forward = forward;
        }

        public void run() {
            if (!this.moving) {
                return;
            }
            if (this.forward) {
                this.sprite.nextFrame();
            } else {
                this.sprite.prevFrame();
            }
        }

        public void forward() {
            this.forward = true;
            this.moving = true;
        }

        public void backward() {
            this.forward = false;
            this.moving = true;
        }

        public void setMoving(boolean isMoving) {
            this.moving = isMoving;
        }
    }

}